
import os
from pathlib import Path

class FluidParameters(object):
    '''The FluidParameters class is used to set fluid simulation parameters. 
    '''
    def __init__(self):
        self.boundary_conditions = self.BoundaryConditions()
        self.fluid_properties = self.FluidProperties()
        self.initial_conditions = self.InitialConditions()
        self.solution = self.Solution()
        self.wall_properties = self.WallProperties()
        self.use_mpi = False 
        self.num_processes = 0 

    def __str__(self):
        return 'sv.simulation.FluidParameters'

    class BoundaryConditions(object):
        '''The BoundaryConditions class is used to set fluid simulation boundary conditions.
        '''
        RESISTANCE = "Resistance"
        PRESCRIBED_VELOCITIES = "Prescribed Velocities"

        def __init__(self):
            self.bc_list = []

        def add_resistance(self, face_name, resistance):
            self.bc_list.append( { 'type': self.RESISTANCE, 'faceID': face_name, 'value':resistance})

        def add_velocities(self, face_name, file_name):
            self.bc_list.append( { 'type': self.PRESCRIBED_VELOCITIES, 'faceID': face_name, 'file_name': file_name} )

    class InitialConditions(object):
        '''The InitialConditions class is used to set fluid simulation initial conditions.
        '''
        def __init__(self):
            self.pressure = 0.0
            self.velocity = [0.0001, 0.0001, 0.0001]

        def __str__(self):
            return ', '.join("%s: %s" % item for item in vars(self).items())

    class FluidProperties(object):
        '''The FluidProperties class is used to define the fluid simulation fluid properties.
        '''
        def __init__(self):
            self.density = 1.06
            self.viscosity = 0.04

    class Solution(object):
        '''The Solution class is used to define the fluid simulation solution parameters.
        '''
        def __init__(self):
            self.time_step = 0.0
            self.num_time_steps = 0.0
            self.restart_write_frequency = 10
            self.output_surface_stress = True
            self.force_calculation_method = 'Velocity Based' 
            self.print_average_solution = True
            self.print_error_indicators = False
            self.step_construction = 2
            self.pressure_coupling = True
            self.backflow_stabilization_coefficient = 0.2 

        def __str__(self):
            return ', '.join("%s: %s" % item for item in vars(self).items())

    class WallProperties(object):
        '''The WallProperties class is used to define the material properties of the solid wall.
        '''
        def __init__(self):
            self.rigid = True
            self.density = 0.0
            self.viscosity = 0.0

        def __str__(self):
            return ', '.join("%s: %s" % item for item in vars(self).items())

        def add_property(self, faceID, e, vis):
            self.density = e

class ROMParameters(object):
    '''The ROMParameters class is used to set reduced-order modeling simulation parameters. 
    '''
    def __init__(self):
        pass 

    def __str__(self):
        return 'sv.simulation.ROMParameters'

    class BoundaryConditions(object):
        '''The BoundaryConditions class is used to set 1D simulation boundary conditions.

        Attributes:
            bc_list (list[dict]): The list of boundary conditions.
            bc_path (str): The path to the boundary conditions files.
        '''

        # BC types.
        BC_TYPE_RCR = "RCR"
        BC_TYPE_RESISTANCE = "Resistance"
        BC_TYPE_PRESCRIBED_VELOCITIES = "Prescribed Velocities"

        # File names storing BC values for each BC type.
        RCR_FILE_NAME = "rcrt.dat"
        RESISTANCE_FILE_NAME = "resistance.dat"

        def __init__(self):
            self.bc_list = []
            self.bc_path = str(Path('.'))

        def add_resistance(self, face_name, resistance):
            self.bc_list.append( { 'type': self.BC_TYPE_RESISTANCE, 'faceID': face_name, 'resistance':resistance})

        def add_rcr(self, face_name, Rp, C, Rd):
            self.bc_list.append( { 'type': self.BC_TYPE_RCR, 'faceID': face_name, 'Rp':Rp, 'C':C, 'Rd':Rd})

        def add_velocities(self, face_name, file_name):
            self.bc_list.append( { 'type': self.BC_TYPE_PRESCRIBED_VELOCITIES, 'faceID': face_name, 'file_name': file_name} )

        def set_path(self, path):
            self.bc_path = path

        def write_files(self, path=None):
            '''Write boundary conditions to files for each specific type.
            '''
            self.write_rcrt_file(path)
            self.write_resistance_file(path)

        def write_resistance_file(self, path=None):
            '''Write RESISTANCE boundary conditions to a file.
            '''
            num_bcs = sum([bc['type'] == self.BC_TYPE_RESISTANCE for bc in self.bc_list])
            if num_bcs == 0:
                return

            if path == None:
                bc_path = self.bc_path
            else:
                bc_path = path

            newline = os.linesep 
            with open(bc_path + os.sep + self.RESISTANCE_FILE_NAME, "w") as res_file:
                for bc in self.bc_list:
                    if bc['type'] != self.BC_TYPE_RESISTANCE:
                        continue
                    res_file.write(bc['faceID'] + ' ' + str(bc['resistance']) + newline) 

        def write_rcrt_file(self, path=None):
            '''Write RCR boundary conditions to a file.
            '''
            num_bcs = sum([bc['type'] == self.BC_TYPE_RCR for bc in self.bc_list])
            if num_bcs == 0:
                return

            if path == None:
                bc_path = self.bc_path
            else:
                bc_path = path

            newline = os.linesep 
            pressure = '0.0'
            with open(bc_path + os.sep + self.RCR_FILE_NAME, "w") as rcr_file:
                rcr_file.write('2' + newline)
                for bc in self.bc_list:
                    if bc['type'] != self.BC_TYPE_RCR:
                        continue
                    rcr_file.write('2' + newline)
                    for pname in ['faceID', 'Rp', 'C', 'Rd']:
                        rcr_file.write(str(bc[pname]) + newline) 
                    rcr_file.write('0.0 ' + pressure + newline) 
                    rcr_file.write('1.0 ' + pressure + newline) 

    class FluidProperties(object):
        '''The FluidProperties class is used to define the 1D simulation fluid properties.
        '''
        def __init__(self):
            self.density = 1.06
            self.viscosity = 0.04

    class ModelParameters(object):
        '''The ModelParameters class is used to define model parameters. 

        Attributes:
            name (str): The model name. 
            surface (vtkPolyData): The model surface.
            outlet_face_names (list[str]): The names of the model outlet faces. 
            inlet_face_names (list[str]): The names of the model inlet faces.
            centerlines_file_name (str): The name of the VTK .vtp file containing centerlines geometry.
        '''
        def __init__(self):
            self.name = None
            self.surface = None
            self.outlet_face_names = None 
            self.inlet_face_names = None 
            self.centerlines_file_name = None 

    class MeshParameters(object):
        '''The MeshParameters class is used to define mesh parameters. 

        Attributes:
            element_size (float): The size of elements used to discretize a segment.
            num_branch_segments (int): The number of segments to use for each branch.
            use_adaptive_meshing (bool): If True then generate a mesh based on model geometry. 
        '''
        def __init__(self):
            self.element_size = 0.1
            self.num_branch_segments = 1
            self.use_adaptive_meshing = False

    class Solution(object):
        '''The Solution class is used to define the 1D simulation solution parameters.
        '''
        def __init__(self):
            self.time_step = 0.0
            self.num_time_steps = 0.0
            self.save_frequency = 20

        def __str__(self):
            return ', '.join("%s: %s" % item for item in vars(self).items())

    class WallProperties(object):
        '''The WallProperties class is used to define the material properties of the solid wall.
        '''
        OLUFSEN_MATERIAL_MODEL = 'OLUFSEN'
        LINEAR_MATERIAL_MODEL = 'LINEAR'
        def __init__(self):
            self.material_model = None

        class LinearMaterial(object):
            '''The LinearMaterial class is used to define the material properties for a linear elastic material model.

            Attributes:
                eh_r (float): The product of elastic modulus and thickness divided by the radius. 
                pressure (float): The material reference pressure. 
            '''
            def __init__(self):
                self.name = ROMParameters.WallProperties.LINEAR_MATERIAL_MODEL
                self.eh_r = 1.0e7
                self.pressure = 0.0

            def __str__(self):
                return 'name: {0:s},  Eh/r: {1:g},  pressure: {2:g}'.format(self.name, self.eh_r, self.pressure)

        class OlufsenMaterial(object):
            '''The OlufsenMaterial class is used to define the material properties for an Olufsen material model.

            The Olufsen material model is an empirically-derived best fit to the equation 

                E*h / r0(z) = k1 * exp(k2*r0(z)) + k3

            for an arterial segment.

            Attributes:
                k1 (float): The k1 constant for the exponential function. 
                k2 (float): The k2 constant for the exponential function. 
                k3 (float): The k3 constant for the exponential function. 
                exponent (float): The material exponent. 
                pressure (float): The material reference pressure. 
            '''
            def __init__(self):
                self.name = ROMParameters.WallProperties.OLUFSEN_MATERIAL_MODEL
                self.k1 = 2e7
                self.k2 = -22.5267 
                self.k3 = 8.65e5
                self.exponent = 1.1 
                self.pressure = 1133240 

            def __str__(self):
                params = 'name:{0:s}, k1:{1:g}, k2:{2:g}, k3:{3:g}, exponent:{4:g}, pressure:{5:g}'
                return params.format(self.name, self.k1, self.k2, self.k3, self.exponent, self.pressure) 
